Developer Documentation

QuickTime 4 API Documentation

Inside Macintosh: QuickTime

Previous | Overview | Contents | Next |

Creating a Movie

Creating a movie involves several steps. You must first create and open the movie file that is to contain the movie. You then create the tracks and media structures for the movie. You then add samples to the media structures. Finally, you add the movie resource to the movie file. The sample program in this section, CreateWayCoolMovie , demonstrates this process.

This program has been divided into several segments. The main segment, CreateMyCoolMovie , creates and opens the movie file, then invokes other functions to create the movie itself. Once the data has been added to the movie, this function saves the movie in its movie file and closes the file.

The CreateMyCoolMovie function uses the CreateMyVideoTrack and CreateMySoundTrack functions to create the movie's tracks. The CreateMyVideoTrack function creates the video track and the media that contains the track's data. It then collects sample data in the media by calling the AddVideoSamplesToMedia function. Note that this function uses the Image Compression Manager. The CreateMySoundTrack function creates the sound track and the media that contains the sound. It then collects sample data by calling the AddSoundSamplesToMedia function.

Note
Throughout this volume, sound track refers to a QuickTime movie track that contains sound--as opposed to a soundtrack, which denotes the entire audio presentation of a movie as filmgoers know it. Consequently, a soundtrack may be made up of one or more QuickTime sound tracks.

A Sample Program for Creating a Movie

The CreateWayCoolMovie program consists of a number of segments, many of which are not included in this sample. Omitted segments deal with general initialization logic and other common aspects of Macintosh programming. The HandleEditMenu function, shown in Listing 5 , has been included here to show how to initialize the Movie Toolbox with the EnterMovies function.

Listing 5 Creating a movie: The main program

#include <Types.h>
#include <Traps.h>
#include <Menus.h>
#include <Packages.h>
#include <Memory.h>
#include <Errors.h>
#include <Fonts.h>
#include <QuickDraw.h>
#include <Resources.h>
#include <GestaltEqu.h>
#include <FixMath.h>
#include <Sound.h>
#include <string.h>


#include "Movies.h"
#include "ImageCompression.h"
    
void CheckError(OSErr error, Str255 displayString)
{
        if (error == noErr) return;
        if (displayString[0] > 0)
            DebugStr(displayString);
        ExitToShell();
}

void InitMovieToolbox (void)
{
        OSErr   err;
    
        InitGraf (&qd.thePort);
        InitFonts ();
        InitWindows ();
        InitMenus ();
        TEInit ();
        InitDialogs (nil);
        err = EnterMovies ();
        CheckError (err, "\pEnterMovies" );
}

void main( void )
{
        InitMovieToolbox ();
        CreateMyCoolMovie ();
}

A Sample Function for Creating and Opening a Movie File

The CreateMyCoolMovie function, shown in Listing 6 , contains the main logic for this program. This function creates and opens a movie file for the new movie. It then establishes a data reference for the movie's data (note that, if your movie's data is stored in the same file as the movie itself, you do not have to create a data reference--set the data reference to 0). This function then calls two other functions, CreateMyVideoTrack and CreateMySoundTrack , to create the tracks for the new movie. Once the tracks have been created, CreateMyCoolMovie adds the new resource to the movie file and closes the movie file.

Listing 6 Creating and opening a movie file

#define kMyCreatorType 'TVOD'

/*
Sample Player's creator type since it is the movie player
of choice. You can use your own creator type, of course.
*/
#define kPrompt "\pEnter movie file name:"

void CreateMyCoolMovie (void)
{
    Point       where = {100,100};
    SFReply     theSFReply;
    Movie       theMovie = nil;
    FSSpec      mySpec;
    short       resRefNum = 0;
    short       resId = 0;
    OSErr       err = noErr;
    SFPutFile (where, "\pEnter movie file name:",
                            "\pMovie File", nil, &theSFReply);
    if (!theSFReply.good) return;

    FSMakeFSSpec(theSFReply.vRefNum, 0,
                                     theSFReply.fName, &mySpec);

    err = CreateMovieFile (&mySpec,
                                'TVOD',
                                smCurrentScript,
                                createMovieFileDeleteCurFile,
                                &resRefNum,
                                &theMovie );
    CheckError(err, "\pCreateMovieFile");

    CreateMyVideoTrack (theMovie);
    CreateMySoundTrack (theMovie);
    
    err = AddMovieResource (theMovie, resRefNum, &resId,
                                         theSFReply.fName);
    CheckError(err, "\pAddMovieResource");
    
    if (resRefNum) CloseMovieFile (resRefNum);
    DisposeMovie (theMovie);
}

The code listing above adds the movie to the resource fork of the file that it creates. It is possible to create a movie file with no resource fork, and to store the movie in the file's data fork.

To create a movie file with no resource fork, pass the createMovieFileDontCreateResFile flag when you call CreateMovieFile . To store the movie into the file's data fork, call AddMovieResource as shown, but pass kResFileNotOpened as the resRefNum parameter, and pass movieInDataForkResID in the ResID parameter.

A Sample Function for Creating a Video Track in a New Movie

The CreateMyVideoTrack function, shown in Listing 7 , creates a video track in the new movie. This function creates the track and its media by calling the NewMovieTrack and NewTrackMedia functions, respectively. This function then establishes a media-editing session and adds the movie's data to the media. The bulk of this work is done by the AddVideoSamplesToMedia subroutine. Once the data has been added to the media, this function adds the media to the track by calling the Movie Toolbox's InsertMediaIntoTrack function (described on InsertMediaIntoTrack ).

Listing 7 Creating a video track

#define     kVideoTimeScale 600
#define     kTrackStart             0
#define     kMediaStart             0
#define     kFix1                   0x00010000
void    CreateMyVideoTrack (Movie theMovie)
{
    Track           theTrack;
    Media           theMedia;
    OSErr           err = noErr;
    Rect            trackFrame = {0,0,100,320};

    theTrack = NewMovieTrack (theMovie,
                                    FixRatio(trackFrame.right,1),
                                    FixRatio(trackFrame.bottom,1),
                                    kNoVolume);
    CheckError( GetMoviesError(), "\pNewMovieTrack" );
    
    theMedia = NewTrackMedia (theTrack, VideoMediaType,
                                    600, // Video Time Scale
                                    nil, 0);
    CheckError( GetMoviesError(), "\pNewTrackMedia" );
    
    err = BeginMediaEdits (theMedia);
    CheckError( err, "\pBeginMediaEdits" );

    AddVideoSamplesToMedia (theMedia, &trackFrame);
    
    err = EndMediaEdits (theMedia);
    CheckError( err, "\pEndMediaEdits" );
    
    err = InsertMediaIntoTrack (theTrack, 0,/* track start time */
                                            0,           /* media start time */
                                            GetMediaDuration (theMedia),
                                            kFix1);
    CheckError( err, "\pInsertMediaIntoTrack" );
}

A Sample Function for Adding Video Samples to a Media

The AddVideoSamplesToMedia function, shown in Listing 8 , creates video data frames, compresses each frame, and adds the frames to the media. This function creates its own video data by calling the DrawAFrame function. Note that this function does not temporally compress the image sequence; rather, the function only spatially compresses each frame individually.

Listing 8 Adding video samples to a media

#define     kSampleDuration             240
        /* video frames last 240 * 1/600th of a second */
#define     kNumVideoFrames             29
#define     kNoOffset                   0
#define     kMgrChoose                  0
#define     kSyncSample                 0
#define     kAddOneVideoSample          1
#define     kPixelDepth                 16

void AddVideoSamplesToMedia (Media theMedia,
                                        const Rect *trackFrame)
{
    long                            maxCompressedSize;
    GWorldPtr                       theGWorld = nil;
    long                            curSample;
    Handle                          compressedData = nil;
    Ptr                             compressedDataPtr;
    ImageDescriptionHandle          imageDesc = nil;
    CGrafPtr                        oldPort;
    GDHandle                        oldGDeviceH;
    OSErr                           err = noErr;

    err = NewGWorld (&theGWorld,
                        16,             /* pixel depth */
                        trackFrame,
                        nil,
                        nil,
                        (GWorldFlags) 0 );
    CheckError (err, "\pNewGWorld");
    
    LockPixels (theGWorld->portPixMap);
    err = GetMaxCompressionSize (theGWorld->portPixMap,
                                    trackFrame,
                                    0, /* let ICM choose depth */
                                    codecNormalQuality,
                                    'rle ',
                                    (CompressorComponent) anyCodec,
                                    &maxCompressedSize);
    CheckError (err, "\pGetMaxCompressionSize" );

    compressedData = NewHandle(maxCompressedSize);
    CheckError( MemError(), "\pNewHandle" );
    
    MoveHHi( compressedData );
    HLock( compressedData );
    compressedDataPtr = StripAddress( *compressedData );

    imageDesc = (ImageDescriptionHandle)NewHandle(4);
    CheckError( MemError(), "\pNewHandle" );
        
    GetGWorld (&oldPort, &oldGDeviceH);
    SetGWorld (theGWorld, nil);
    
    for (curSample = 1; curSample < 30; curSample++)
    {
        EraseRect (trackFrame);
        DrawFrame(trackFrame, curSample);

        err = CompressImage (theGWorld->portPixMap,
                                    trackFrame,
                                    codecNormalQuality,
                                    'rle ',
                                    imageDesc,
                                    compressedDataPtr );
        CheckError( err, "\pCompressImage" );
        
        err = AddMediaSample(theMedia,
                                    compressedData,
                                    0,      /* no offset in data */
                                    (**imageDesc).dataSize,
                                    60,     /* frame duration = 1/10 sec */
                                    (SampleDescriptionHandle)imageDesc,
                                    1,      /* one sample */
                                    0,      /* self-contained samples */
                                    nil);
        CheckError( err, "\pAddMediaSample" );
    }
    SetGWorld (oldPort, oldGDeviceH);
    
    if (imageDesc) DisposeHandle ((Handle)imageDesc);
    if (compressedData) DisposeHandle (compressedData);
    if (theGWorld) DisposeGWorld (theGWorld);
}

A Sample Function for Creating Video Data for a Movie

The DrawAFrame function, shown in Listing 9 , creates video data for this movie. This function draws a different frame each time it is invoked, based on the sample number, which is passed as a parameter.

Listing 9 Creating video data

void DrawFrame (const Rect *trackFrame, long curSample)
{
    Str255 numStr;

    ForeColor( redColor );
    PaintRect( trackFrame );

    ForeColor( blueColor );
    NumToString (curSample, numStr);
    MoveTo ( trackFrame->right / 2, trackFrame->bottom / 2);
    TextSize ( trackFrame->bottom / 3);
    DrawString (numStr);
}

A Sample Function for Creating a Sound Track

The CreateMySoundTrack function, shown in Listing 10 , creates the movie's sound track. This sound track is not synchronized to the video frames of the movie--rather, it is just a separate sound track that accompanies the video data. This function relies upon an 'snd ' resource for its source sound. The CreateMySoundTrack function uses the CreateSoundDescription function to create the sound description structure for these samples.

As with the CreateMyVideoTrack function discussed earlier, this function creates the track and its media by calling the NewMovieTrack and NewTrackMedia functions, respectively. This function then establishes a media-editing session and adds the movie's data to the media. This function adds the sound samples using a single invocation of the AddMediaSample function. This is possible because all the sound samples are the same size and rely on the same sample description (the SoundDescription structure). If you use this approach, it is often advisable to break up the sound data in the movie, so that the movie plays smoothly. After you create the movie, you can call the FlattenMovie function (described on FlattenMovie ) to create an interleaved version of the movie. Another approach is to call AddMediaSample multiple times, breaking the sound into multiple chunks at that point.

Once the data has been added to the media, this function adds the media to the track by calling the Movie Toolbox's InsertMediaIntoTrack function (described on InsertMediaIntoTrack ).

Listing 10 Creating a sound track

#define     kSoundSampleDuration 1
#define     kSyncSample 0
#define     kTrackStart     0
#define     kMediaStart     0
#define     kFix1           0x00010000


void CreateMySoundTrack (Movie theMovie)
{
    Track                               theTrack;
    Media                               theMedia;
    Handle                              sndHandle = nil;
    SoundDescriptionHandle              sndDesc = nil;
    long                                sndDataOffset;
    long                                sndDataSize;
    long                                numSamples;
    OSErr                               err = noErr;


    sndHandle = GetResource ('snd ', 128);
    CheckError (ResError(), "\pGetResource" );
    if (sndHandle == nil) return;

    sndDesc = (SoundDescriptionHandle) NewHandle(4);
    CheckError (MemError(), "\pNewHandle" );
    
    CreateSoundDescription (sndHandle,
                            sndDesc,
                            &sndDataOffset,
                            &numSamples,
                            &sndDataSize );
                            
    theTrack = NewMovieTrack (theMovie, 0, 0, kFullVolume);
    CheckError (GetMoviesError(), "\pNewMovieTrack" );
    
    theMedia = NewTrackMedia (theTrack, SoundMediaType,
                                        FixRound ((**sndDesc).sampleRate),
                                        nil, 0);
    CheckError (GetMoviesError(), "\pNewTrackMedia" );

    err = BeginMediaEdits (theMedia);
    CheckError( err, "\pBeginMediaEdits" );

    err = AddMediaSample(theMedia,
                    sndHandle,
                    sndDataOffset,      /* offset in data */
                    sndDataSize,
                    1,                  /* duration of each sound sample */
                    (SampleDescriptionHandle) sndDesc,
                    numSamples,
                    0,                  /* self-contained samples */
                    nil );
    CheckError( err, "\pAddMediaSample" );
                    
    err = EndMediaEdits (theMedia);
    CheckError( err, "\pEndMediaEdits" );

    err = InsertMediaIntoTrack (theTrack,
                                    0,          /* track start time */
                                    0,          /* media start time */
                                    GetMediaDuration (theMedia),
                                    kFix1);
    CheckError( err, "\pInsertMediaIntoTrack" );

    if (sndDesc != nil) DisposeHandle( (Handle)sndDesc);
}

A Sample Function for Creating a Sound Description Structure

The CreateSoundDescription function, shown in Listing 11 , creates a sound description structure that correctly describes the sound samples obtained from the 'snd ' resource. This function can handle all the sound data formats that are possible in the sound resource. This function uses the GetSndHdrOffset function to locate the sound data in the sound resource.

Listing 11 Creating a sound description

/* Constant definitions */
/*
    for the following constants, please consult the Macintosh
    Audio Compression and Expansion Toolkit
*/
#define kMACEBeginningNumberOfBytes 6
#define kMACE31MonoPacketSize 2
#define kMACE31StereoPacketSize 4
#define kMACE61MonoPacketSize 1
#define kMACE61StereoPacketSize 2

void CreateSoundDescription (Handle sndHandle,
                                SoundDescriptionHandlesndDesc,
                                long *sndDataOffset,
                                long *numSamples,
                                long *sndDataSize )
{
    long                        sndHdrOffset = 0;
    long                        sampleDataOffset;
    SoundHeaderPtr              sndHdrPtr = nil;
    long                        numFrames;
    long                        samplesPerFrame;
    long                        bytesPerFrame;
    SignedByte                  sndHState;
    SoundDescriptionPtr         sndDescPtr;
    
    *sndDataOffset = 0;
    *numSamples = 0;
    *sndDataSize = 0;

    SetHandleSize( (Handle)sndDesc, sizeof(SoundDescription) );
    CheckError(MemError(),"\pSetHandleSize");
    sndHdrOffset = GetSndHdrOffset (sndHandle);
    if (sndHdrOffset == 0) CheckError(-1, "\pGetSndHdrOffset ");

                /* we can use pointers since we don't move memory */
    sndHdrPtr = (SoundHeaderPtr) (*sndHandle + sndHdrOffset);
    sndDescPtr = *sndDesc;
    
    sndDescPtr->descSize = sizeof (SoundDescription);
                /* total size of sound description structure */
    sndDescPtr->resvd1 = 0;     
    sndDescPtr->resvd2 = 0;
    sndDescPtr->dataRefIndex = 1;
    sndDescPtr->compressionID = 0;
    sndDescPtr->packetSize = 0;
    sndDescPtr->version = 0;        
    sndDescPtr->revlevel = 0;
    sndDescPtr->vendor = 0;
    
    switch (sndHdrPtr->encode)
    {
        case stdSH:
            sndDescPtr->dataFormat = 'raw ';
                /* uncompressed offset-binary data */
            sndDescPtr->numChannels = 1;
                /* number of channels of sound */
            sndDescPtr->sampleSize = 8;
                /* number of bits per sample */
            sndDescPtr->sampleRate = sndHdrPtr->sampleRate;
                /* sample rate */
            *numSamples         = sndHdrPtr->length;
            *sndDataSize        = *numSamples;
            bytesPerFrame       = 1;
            samplesPerFrame = 1;
            sampleDataOffset = (Ptr)&sndHdrPtr->sampleArea
                                                            - (Ptr)sndHdrPtr;
            break;      

        case extSH:
        {
            ExtSoundHeaderPtr           extSndHdrP;

            extSndHdrP = (ExtSoundHeaderPtr)sndHdrPtr;
            sndDescPtr->dataFormat = 'raw ';
                                    /* uncompressed offset-binary data */
            sndDescPtr->numChannels = extSndHdrP->numChannels;
                                    /* number of channels of sound */
            sndDescPtr->sampleSize = extSndHdrP->sampleSize;
                                    /* number of bits per sample */
            sndDescPtr->sampleRate = extSndHdrP->sampleRate;
                                    /* sample rate */
            numFrames = extSndHdrP->numFrames;
            *numSamples = numFrames;
            bytesPerFrame = extSndHdrP->numChannels *
                                            ( extSndHdrP->sampleSize / 8);
            samplesPerFrame = 1;
            *sndDataSize = numFrames * bytesPerFrame;
            sampleDataOffset = (Ptr)(&extSndHdrP->sampleArea)
                                                             - (Ptr)extSndHdrP;
        }
            break;
            
        case cmpSH:
        {
            CmpSoundHeaderPtr cmpSndHdrP;

            cmpSndHdrP = (CmpSoundHeaderPtr)sndHdrPtr;
            sndDescPtr->numChannels = cmpSndHdrP->numChannels;
                    /* number of channels of sound */
            sndDescPtr->sampleSize = cmpSndHdrP->sampleSize;
                    /* number of bits per sample before compression */
            sndDescPtr->sampleRate = cmpSndHdrP->sampleRate;
                    /* sample rate */
            numFrames = cmpSndHdrP->numFrames;
            sampleDataOffset =(Ptr)(&cmpSndHdrP->sampleArea)
                                                            - (Ptr)cmpSndHdrP;
            switch (cmpSndHdrP->compressionID)
            {
                case threeToOne:
                    sndDescPtr->dataFormat = 'MAC3';
                    /* compressed 3:1 data */
                    samplesPerFrame = kMACEBeginningNumberOfBytes;
                    *numSamples = numFrames * samplesPerFrame;
                    switch (cmpSndHdrP->numChannels)
                    {
                        case 1:
                            bytesPerFrame = cmpSndHdrP->numChannels
                                                * kMACE31MonoPacketSize;
                            break;
                        case 2:
                            bytesPerFrame = cmpSndHdrP->numChannels
                                                * kMACE31StereoPacketSize;
                            break;
                        default:
                            CheckError(-1, "\pCorrupt sound data" );
                            break;
                    }
                    *sndDataSize = numFrames * bytesPerFrame;
                    break;
                case sixToOne:
                    sndDescPtr->dataFormat = 'MAC6';
                    /* compressed 6:1 data */
                    samplesPerFrame = kMACEBeginningNumberOfBytes;
                    *numSamples = numFrames * samplesPerFrame;
                    switch (cmpSndHdrP->numChannels)
                    {
                        case 1:
                            bytesPerFrame = cmpSndHdrP->numChannels
                                                * kMACE61MonoPacketSize;
                            break;
                        case 2:
                            bytesPerFrame = cmpSndHdrP->numChannels
                                                    * kMACE61StereoPacketSize;
                            break;
                        default:
                            CheckError(-1, "\pCorrupt sound data" );
                            break;
                    }
                    *sndDataSize = (*numSamples) * bytesPerFrame;
                    break;
                default:
                    CheckError(-1, "\pCorrupt sound data" );
                    break;
            }
            }           /* switch cmpSndHdrP->compressionID:*/
            break;      /* of cmpSH: */
            
        default:
            CheckError(-1, "\pCorrupt sound data" );
            break;
            
    }                   /* switch sndHdrPtr->encode */
    *sndDataOffset = sndHdrOffset + sampleDataOffset;
}

Parsing a Sound Resource

The GetSndHdrOffset function, shown in Listing 12 , parses the specified sound resource and locates the sound data stored in the resource. The GetSndHdrOffset function cruises through a specified 'snd ' resource. It locates the sound data, if any, and returns its type, offset, and size into the resource.

The GetSndHdrOffset function returns an offset instead of a pointer so that the data is not locked in memory. By returning an offset, the calling function can decide when and if it wants the resource locked down to access the sound data.

The first step in finding this data is to determine if the 'snd ' resource is format (type) 1 or format (type) 2. A type 2 is easy, but a type 1 requires that you find the number of 'snth' resource types specified and then skip over each one, including the init option. Once you do this, you have a pointer to the number of commands in the 'snd ' resource. When the function finds the first one, it examines the command to find out if it is a sound data command. Since it is a sound resource, the command also has its dataPointerFlag parameter set to 1. When the function finds a sound data command, it returns its offset and type, and exits.

Warning
Do not send the GetSndHdrOffset function a nil handle; if you do, your system will crash.

Listing 12 Parsing a sound resource

typedef SndCommand *SndCmdPtr;

typedef struct
{
    short       format;
    short       numSynths;
} Snd1Header, *Snd1HdrPtr, **Snd1HdrHndl;

typedef struct
{
    short       format;
    short       refCount;
} Snd2Header, *Snd2HdrPtr, **Snd2HdrHndl;
typedef struct
{
    short       synthID;
    long        initOption;
} SynthInfo, *SynthInfoPtr;


long GetSndHdrOffset (Handle sndHandle)
{
    short howManyCmds;
    long sndOffset = 0;
    Ptr sndPtr;
    
    if (sndHandle == nil) return 0;
    sndPtr = *sndHandle;
    if (sndPtr == nil) return 0;
    
    if ((*(Snd1HdrPtr)sndPtr).format == firstSoundFormat)
    {
        short synths = ((Snd1HdrPtr)sndPtr)->numSynths;
        sndPtr += sizeof(Snd1Header) + (sizeof(SynthInfo) * synths);
    } else
    {
        sndPtr += sizeof(Snd2Header);
    }
    
    howManyCmds = *(short *)sndPtr;
    
    sndPtr += sizeof(howManyCmds);
    /*
    sndPtr is now at the first sound command--cruise all
    commands and find the first soundCmd or bufferCmd
    */
    while (howManyCmds > 0)
    {
        switch (((SndCmdPtr)sndPtr)->cmd)
        {
            case (soundCmd + dataOffsetFlag):
            case (bufferCmd + dataOffsetFlag):
                sndOffset = ((SndCmdPtr)sndPtr)->param2;
                howManyCmds = 0;/* done, get out of loop */
                break;
            default:                /* catch any other type of commands */
                sndPtr += sizeof(SndCommand);
                howManyCmds--;
                break;
        }
    }                               /* done with all commands */

    return sndOffset;
}       /* of GetSndHdrOffset */

© 1999 Apple Computer, Inc.

Previous | Overview | Contents | Next